/*
 * IM - Image I/O Library
 * Copyright (c) 1996 CENPES/Petrobras, TeCGraf/PUC-Rio.
 * Antonio E. Scuri - scuri@tecgraf.puc-rio.br
 * 
 * IMRAS.C
 * 
 * Contains the implementation of the RAS file format functions.
 *
 * History:
 *          1996.04.05 - Changed the structure of the module to the new format.
 *          1996.06.14 - Finished inicial version.
 *
 */

#include "im.h"
#include "imio.h"
#include "imras.h"
#include "binfile.h"


#include <stdlib.h>
#include <memory.h>


/*  File Header Structure. */
/*	4	Magic;		  magic number */
/*	4	Width;		  width (pixels) of image */
/*	4	Height;		  height (pixels) of image */
/*	4	Depth;		  depth (1, 8, or 24 bits, or 32?) of pixel */
/*	4	Length;		  length (bytes) of image */
/*	4	Type;	      type of file; see RT_OLD below */
/*	4	MapType;		type of colormap; see RAS_NONE below */
/*	4	MapLength;  length (bytes) of following map */
/*  32  */

#define	RAS_ID	0x59A66A95

/* Sun supported ras_type's */
#define RAS_OLD		0	/* Raw pixrect image in 68000 byte order */
#define RAS_STANDARD	1	/* Raw pixrect image in 68000 byte order */
#define RAS_BYTE_ENCODED	2	/* Run-length compression of bytes */
#define RAS_EXPERIMENTAL 0xffff	/* Reserved for testing */

#define RAS_ESCAPE  128

/* Sun supported ras_maptype's */
#define RAS_NONE	0	    /* ras_maplength is expected to be 0 */
#define RAS_EQUAL_RGB	1	/* red[ras_maplength/3],green[],blue[] */
#define RAS_RAW		2     /* Sun registered ras_maptype's */


/* NOTES:
 * 	Each line of the image is rounded out to a multiple of 16 bits.
 *   This corresponds to the rounding convention used by the memory pixrect
 *   package (/usr/include/pixrect/memvar.h) of the SunWindows system.
 *	The ras_encoding field (always set to 0 by Sun's supported software)
 *   was renamed to ras_length in release 2.0.  As a result, rasterfiles
 *   of type 0 generated by the old software claim to have 0 length; for
 *   compatibility, code reading rasterfiles must be prepared to compute the
 *   true length from the width, height, and depth fields. */


typedef struct _imFileRAS
{
  binFile* file_bin;          /* the binary file handle */
  unsigned long bpp,          /* number of bits per pixel */
                type,         /* ras compression information */
                map_type,     /* palette information */
                line_size;    /* line buffer size */
  unsigned char* line_buffer; /* line buffer */
} imFileRAS;


static long imRASScanBytes(int width, int bpp)
{
	return ((width * bpp + 15) / 16) * 2;
}


static int imRASDecodeScanLine(binFile* file_bin, unsigned char* DecodedBuffer, int Width)
{
	int index = 0, space;
	unsigned char count = 0;
	unsigned char value = 0;

	while (index < Width)
	{
    binFileRead1ByteBuf(file_bin, &value, 1);

		if (value == RAS_ESCAPE)
		{
      binFileRead1ByteBuf(file_bin, &count, 1);

			if (count != 0)
			{
				if (index >= Width)
          return IM_ERR_READ;

        binFileRead1ByteBuf(file_bin, &value, 1);

				count++;

				space = Width - index;
				if (count > space)
					count = (unsigned char)space;

				while (count--)
				{
					*DecodedBuffer++ = value;
					index++;
				}
			}
			else
			{
				*DecodedBuffer++ = RAS_ESCAPE;
				index++;
			}
    }
		else
		{
			*DecodedBuffer++ = value;
			index++;
		}

    if (binFileError(file_bin))
      return IM_ERR_READ;
	}

	return IM_ERR_NONE;
}


static imFile* imRASReadOpen(char* filename, int *err)
{
  unsigned long dword_value;
  imFile *ifile = (imFile*)malloc(sizeof(imFile));
  imFileRAS *file_ras = (imFileRAS*)malloc(sizeof(imFileRAS));
  binFile* file_bin;

  ifile->file = file_ras;

  /* opens the binary file for reading with motorola byte order */
  file_bin = binFileOpen(filename, 0, 1);
  if (file_bin == NULL)
  {
    free(file_ras);
    free(ifile);
    if (err) *err = IM_ERR_OPEN;
    return NULL;
  }

  file_ras->file_bin = file_bin;

  /* reads the RAS format identifier */
  binFileRead4BytesBuf(file_bin, &dword_value, 1);
  if (binFileError(file_bin))
  {
    binFileClose(file_bin);
    free(file_ras);
    free(ifile);
    if (err) *err = IM_ERR_READ;
    return NULL;
  }

  if (dword_value != RAS_ID)
  {
    binFileClose(file_bin);
    free(file_ras);
    free(ifile);
    if (err) *err = IM_ERR_FORMAT;
    return NULL;
  }

  /* reads the image width */
  binFileRead4BytesBuf(file_bin, &dword_value, 1);
  ifile->width = dword_value;

  /* reads the image height */
  binFileRead4BytesBuf(file_bin, &dword_value, 1);
  ifile->height = dword_value;

  /* reads the number of bits per pixel */
  binFileRead4BytesBuf(file_bin, &file_ras->bpp, 1);

  if (binFileError(file_bin))
  {
    binFileClose(file_bin);
    free(file_ras);
    free(ifile);
    if (err) *err = IM_ERR_READ;
    return NULL;
  }

  if (file_ras->bpp > 8)
    ifile->type = IM_RGB;
  else
    ifile->type = IM_MAP;

  switch (file_ras->bpp)
    {
    case 1:
      ifile->pal_size = 2;
      break;
    case 8:
      ifile->pal_size = 256;
      break;
    case 24:
    case 32:
      ifile->pal_size = 0;
      break;
    default:
      file_ras->line_buffer = NULL;
      ifile->pal_size = 0;
      if (err) *err = IM_ERR_TYPE;
      return ifile;
    }

  /* jump 4 bytes */
  binFileSeekOffset(file_bin, 4);

  /* reads the compression information */
  binFileRead4BytesBuf(file_bin, &file_ras->type, 1);
  ifile->compress = (file_ras->type == RAS_BYTE_ENCODED)? IM_DEFAULT: IM_NONE;

  /* reads the palette information */
  binFileRead4BytesBuf(file_bin, &file_ras->map_type, 1);

  /* reads the palette size */
  binFileRead4BytesBuf(file_bin, &dword_value, 1);

  /* updates the pal_size based on the palette size */
  if (ifile->type == IM_MAP && file_ras->map_type != RAS_NONE)
    ifile->pal_size = dword_value / 3;

  if (binFileError(file_bin))
  {
    binFileClose(file_bin);
    free(file_ras);
    free(ifile);
    if (err) *err = IM_ERR_READ;
    return NULL;
  }

  /* now initialize the imFile control data */

  ifile->split = 0;     /* image is RGB packed */
  ifile->planes[0] = 0; /* sets the default plane order since split = 0 */
  ifile->planes[1] = 1;
  ifile->planes[2] = 2;
  ifile->cur_line = ifile->height - 1;  /* first line */
  ifile->pre_colors = 1;  /* color map is before the image */

  /* Calculate the size of the scan line buffer in bytes */
  file_ras->line_size = imRASScanBytes(ifile->width, file_ras->bpp);
  file_ras->line_buffer = (unsigned char*)malloc(file_ras->line_size);

  if (err) *err = IM_ERR_NONE;

  return ifile;
}


static imFile* imRASWriteOpen(char* filename, int width, int height, int type, int compress, int pal_size, int *err)
{
  unsigned long dword_value;
  imFile *ifile = (imFile*)malloc(sizeof(imFile));
  imFileRAS *file_ras = (imFileRAS*)malloc(sizeof(imFileRAS));
  binFile* file_bin;

  ifile->file = file_ras;

  /* opens the binary file for writing with motorola byte order */
  file_bin = binFileOpen(filename, 1, 1);
  if (file_bin == NULL)
  {
    free(file_ras);
    free(ifile);
    if (err) *err = IM_ERR_OPEN;
    return NULL;
  }

  file_ras->file_bin = file_bin;

  ifile->width = width;
  ifile->height = height;
  ifile->type = type;
  ifile->pal_size = (type == IM_RGB)? 0: pal_size;
  ifile->compress = IM_NONE;

  /* now initialize the imFile control data */

  ifile->split = 0;     /* image is RGB packed */
  ifile->planes[0] = 0; /* sets the default plane order since split = 0 */
  ifile->planes[1] = 1;
  ifile->planes[2] = 2;
  ifile->cur_line = ifile->height - 1;  /* first line */
  ifile->pre_colors = 1;  /* color map is before the image */

  /* we do not know how to compress the RAS image */
  if (compress == IM_COMPRESSED)
  {
    binFileClose(file_bin);
    free(file_ras);
    free(ifile);
    if (err) *err = IM_ERR_COMP;
    return NULL;
  }

  file_ras->bpp = (unsigned short)((ifile->type == IM_RGB)? 24:8);
  file_ras->type = RAS_STANDARD;
  file_ras->map_type = (ifile->type == IM_RGB)? RAS_NONE: RAS_EQUAL_RGB;

  /* writes the RAS file header */

  dword_value = RAS_ID;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* identifier */
  dword_value = ifile->width;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* image width */
  dword_value = ifile->height;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* image height */
  dword_value = file_ras->bpp;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* bits per pixel */
  dword_value = ifile->width * ifile->height * ((ifile->type == IM_RGB)? 3: 1);
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* image lenght */
  dword_value = file_ras->type;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* comrepssion information */
  dword_value = file_ras->map_type;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* palette information */
  dword_value = ifile->pal_size * 3;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* palette lenght */

  /* tests if everything was ok */
  if (binFileError(file_bin))
  {
    binFileClose(file_bin);
    free(file_ras);
    free(ifile);
    if (err) *err = IM_ERR_WRITE;
    return NULL;
  }

  /* Calculate the size of the scan line buffer in bytes */
  file_ras->line_size = imRASScanBytes(ifile->width, file_ras->bpp);
  /* the comppression buffer needs to be bigger than the image line */
  file_ras->line_buffer = (unsigned char*)malloc(file_ras->line_size + 512);

  if (err) *err = IM_ERR_NONE;

  return ifile;
}


static void imRASClose(imFile* ifile)
{
  imFileRAS *file_ras = (imFileRAS*)ifile->file;
  binFile* file_bin = file_ras->file_bin;

  binFileClose(file_bin);

  if (file_ras->line_buffer) free(file_ras->line_buffer);
  free(file_ras);
  free(ifile);
}


static int imRASReadColors(imFile* ifile, long *colors)
{
  int c, i;
  imFileRAS *file_ras = (imFileRAS*)ifile->file;
  binFile* file_bin = file_ras->file_bin;

	if (file_ras->map_type != RAS_NONE)
	{
    unsigned char ras_colors[256 * 3];

    /* reads the color palette */
    binFileRead1ByteBuf(file_bin, ras_colors, ifile->pal_size * 3);

    if (binFileError(file_bin))
      return IM_ERR_READ;

    /* convert the color map to the IM format */
    for (c = 0; c < ifile->pal_size; c++)
    {
      if (file_ras->map_type == RAS_RAW)
      {
        i = c * 3;
        colors[c] = imEncodeColor(ras_colors[i], ras_colors[i+1], ras_colors[i+2]);
      }
      else
      {
				colors[c] = imEncodeColor(ras_colors[c], ras_colors[c+ifile->pal_size], ras_colors[c+2*ifile->pal_size]);
      }
    }
  }
  else
  {
		unsigned char Value;
		double Norm = 255.0 / (ifile->pal_size - 1.0);

		for (c = 0; c < ifile->pal_size; c++)
		{
			Value = (unsigned char)(Norm * c);
			colors[c] = imEncodeColor(Value, Value, Value);
		}
  }

  return IM_ERR_NONE;
}


static int imRASWriteColors(imFile* ifile, long *colors)
{
  int c;
  unsigned char ras_colors[256 * 3];
  imFileRAS *file_ras = (imFileRAS*)ifile->file;
  binFile* file_bin = file_ras->file_bin;

  /* convert the color map to the IM format */
  for (c = 0; c < ifile->pal_size; c++)
  {
    imDecodeColor(&ras_colors[c], &ras_colors[c+ifile->pal_size], &ras_colors[c+2*ifile->pal_size], colors[c]);
  }

  /* writes the color palette */
  binFileWrite1ByteBuf(file_bin, ras_colors, ifile->pal_size * 3);

  if (binFileError(file_bin))
    return IM_ERR_WRITE;

  return IM_ERR_NONE;
}


static int imRASReadLine(imFile* ifile, unsigned char *map)
{
  int x;
  imFileRAS *file_ras = (imFileRAS*)ifile->file;
  binFile* file_bin = file_ras->file_bin;

  /* read and decompress the data */
  if (file_ras->type != RAS_BYTE_ENCODED)
  {
    binFileRead1ByteBuf(file_bin, file_ras->line_buffer, file_ras->line_size);
    if (binFileError(file_bin))
      return IM_ERR_READ;     
  }
  else
  {
    if (imRASDecodeScanLine(file_bin, file_ras->line_buffer, ifile->width) == IM_ERR_READ)
      return IM_ERR_READ;     
  }

  /* convert the buffer in the ras bpp to im 8 bpp. */
  switch (file_ras->bpp)
  {
  case 1:
    for (x = 0; x < ifile->width; x++)
      map[x] = (unsigned char)(((file_ras->line_buffer[x / 8] >> (7 - x % 8)) & 0x01)? 0: 1); /* the bits are inverted */
    break;
  case 8:
    memcpy(map, file_ras->line_buffer, ifile->width);
    break;
  }

  /* we have just read one more line */
  ifile->cur_line--;

  return IM_ERR_NONE;
}


static int imRASWriteLine(imFile* ifile, unsigned char *map)
{
  imFileRAS *file_ras = (imFileRAS*)ifile->file;
  binFile* file_bin = file_ras->file_bin;

  memcpy(file_ras->line_buffer, map, ifile->width);

  binFileWrite1ByteBuf(file_bin, file_ras->line_buffer, file_ras->line_size);
  if (binFileError(file_bin))
    return IM_ERR_WRITE;     

  /* we have just write one more line */
  ifile->cur_line--;

  return IM_ERR_NONE;
}


static int imRASReadLineRGB(imFile* ifile, unsigned char *red, unsigned char *green, unsigned char *blue)
{
  int x, xpos;
  imFileRAS *file_ras = (imFileRAS*)ifile->file;
  binFile* file_bin = file_ras->file_bin;

  /* read and decompress the data */
  if (file_ras->type != RAS_BYTE_ENCODED)
  {
    binFileRead1ByteBuf(file_bin, file_ras->line_buffer, file_ras->line_size);
    if (binFileError(file_bin))
      return IM_ERR_READ;     
  }
  else
  {
    if (imRASDecodeScanLine(file_bin, file_ras->line_buffer, ifile->width*3) == IM_ERR_READ)
      return IM_ERR_READ;     
  }

  /* convert the ras line to the IM line */
  for (x = 0; x < ifile->width; x++)
  {
    xpos = x * (file_ras->bpp == 24? 3: 4);
    blue[x]  = file_ras->line_buffer[xpos];
    green[x] = file_ras->line_buffer[xpos + 1];
    red[x]   = file_ras->line_buffer[xpos + 2];
  }

  /* we have just read one more line */
  ifile->cur_line--;

  return IM_ERR_NONE;
}


static int imRASWriteLineRGB(imFile* ifile, unsigned char *red, unsigned char *green, unsigned char *blue)
{
  int x, xpos;
  imFileRAS *file_ras = (imFileRAS*)ifile->file;
  binFile* file_bin = file_ras->file_bin;

  for (x = 0; x < ifile->width; x++)
  {
    xpos = x * 3;
    file_ras->line_buffer[xpos]     = blue[x];
    file_ras->line_buffer[xpos + 1] = green[x];
    file_ras->line_buffer[xpos + 2] = red[x];
  }

  /* writes the image line data */
  binFileWrite1ByteBuf(file_bin, file_ras->line_buffer, file_ras->line_size);
  if (binFileError(file_bin))
    return IM_ERR_WRITE;

  /* we have just write one more line */
  ifile->cur_line--;

  return IM_ERR_NONE;
}


static int imRASRegisterCallback(imCallback cb, int id)
{
  (void)cb;
  (void)id;
  /* callback not found */
  return 0;
}


static imFormat _imFormatRAS =
{
  0,
  imRASRegisterCallback,
  imRASReadOpen,
  imRASWriteOpen,
  imRASClose,
  imRASReadColors,
  imRASWriteColors,
  imRASReadLine,
  imRASWriteLine,
  imRASReadLineRGB,
  imRASWriteLineRGB
};

imFormat* imFormatRAS(void)
{
  return &_imFormatRAS;
}
